import random
from arbre import Thread

class Processor:
  def __init__(self):
    self.busy=False

  def process(self, thread):
    if thread.blocked:
      print 'Thread cannot be executed, it is blocked'
      return
    if thread.terminated:
      print 'Thread cannot be executed, it is terminated'
      return
    thread.invoke()

class Scheduler:
  def __init__(self):
    self.threads=[]
    self.processors=[]

  def dump(self):
    print 'Dump:'
    for thread in self.threads:
      print thread
    print '.'

  def addThread(self, receiver, method, args, depends=None, parent=None, master=None, nested=False):
    #print 'addThread', defc, callc, blockc, args, ep, depends
    thread=Thread(self, receiver, method, args, depends=depends, parent=parent, master=master, nested=nested)
    self.threads.append(thread)
    return thread

  def tick(self):
    #print 'tick...'
    thread=self.selectThread()
    processor=self.selectProcessor(thread)
    if thread and processor:
      processor.process(thread)
      return True
    else:
      return False

  def run(self):
    active=True
    while active:
      active=self.tick()
      self.propagateSignals()
      self.reap()
#      self.dump()
    print 'exiting with', len(self.threads), 'threads left'
    for thread in self.threads:
      print thread

  def propagateSignals(self):
    print 'propagateSignals'
    working=True
    while working:
      working=False
      for thread in self.threads:
        stillBlocked=False
        foundReturn=None
        foundError=None
        if thread.terminated:
          for dep in thread.depends:
            if not dep.terminated:
              print 'Terminating children:', thread
              print dep
              print '.'
              dep.terminate()
              working=True
        else:
          for dep in thread.depends:
            if not dep.terminated:
              stillBlocked=True
              break
            if dep.error and not foundError:
              foundError=dep
            if (dep.nested or thread.chained) and dep.returning and not foundReturn:
              foundReturn=dep
          if thread.blocked and not stillBlocked:
            print 'unblocking'
            thread.blocked=False
            working=True

          if foundError:
            working=True
            print 'throwing exception'
            thread.terminate(foundError.exitValue, error=foundError.error, errmsg=foundError.errmsg)
          elif foundReturn:
            working=True
            print 'returning', foundReturn.exitValue, foundReturn
            thread.terminate(foundReturn.exitValue)
          elif thread.chained and not thread.blocked and not thread.terminated:
            print 'unchaining', thread.method
#            for dep in thread.depends:
#              print dep
#            print '.'
            working=True
            dep=thread.depends[-1]
            thread.terminate(dep.exitValue, error=dep.error, errmsg=dep.errmsg)

  def reap(self):
    dead=[]
    for x in range(len(self.threads)):
      if self.threads[x].terminated:
        dead.append(x)
    dead.reverse()
    for x in dead:
      print 'reaping', self.threads[x].method
      del self.threads[x]

class SimpleScheduler(Scheduler):
  def __init__(self):
    Scheduler.__init__(self)
    self.processors.append(Processor())

  def selectThread(self):
    for thread in self.threads:
      if not thread.blocked and not thread.terminated:
        return thread
    return None

  def selectProcessor(self, thread):
    for processor in self.processors:
      if not processor.busy:
        return processor
    return None

class RandomScheduler(Scheduler):
  def __init__(self):
    Scheduler.__init__(self)
    self.processors.append(Processor())

  def selectThread(self):
    activeThreads=[]
    for thread in self.threads:
      if not thread.blocked and not thread.terminated:
        activeThreads.append(thread)
    if len(activeThreads)==0:
      return None
    elif len(activeThreads)==1:
      return activeThreads[0]
    else:
      r=random.randrange(len(activeThreads))
      return activeThreads[r]

  def selectProcessor(self, thread):
    for processor in self.processors:
      if not processor.busy:
        return processor
    return None
